﻿using System;
using System.IO;
using System.Web.Mvc;
using System.Web.Routing;
using Smarly.Bussines;

namespace Smarly.Web.ActionFilters
{
	public class ChapterViewCacheAttribute : ActionFilterAttribute
	{
		private bool IsDebug =
#if DEBUG
      true;
#else
			false;
#endif

		class ContextResultObject
		{
			private const char EtagSplitter = ':';

			internal ContentResult Content { get; set; }

			internal string Hash { get; set; }
			internal DateTime UpdateDate { get; set; }

			internal string Etag
			{
				get { return Hash + EtagSplitter + UpdateDate.Ticks; }
			}
		}


		private static readonly SmarlyQueue<ContextResultObject> _queue = new SmarlyQueue<ContextResultObject>(20);
		public string VaryByParam { get; set; }
		public string HashByViewData { get; set; }
		public string UpdateDateByViewData { get; set; }

		public bool Clear { get; set; }
		public bool Remove { get; set; }

		private string GetName(RouteData routeData)
		{
			var result = routeData.Values[VaryByParam] as string;
			return string.IsNullOrWhiteSpace(result) ? null : result.Trim();
		}

		public override void OnActionExecuting(ActionExecutingContext filterContext)
		{
			if (IsDebug)
				return;

			var name = GetName(filterContext.RouteData);
			if (filterContext.HttpContext.User.Identity.IsAuthenticated)
			{
				if (Clear)
					_queue.Clear();

				if (Remove)
					_queue.Remove(name);

				filterContext.HttpContext.Response.ExpireUserEtag();
				base.OnActionExecuting(filterContext);
				return;
			}

			ContextResultObject content = null;
			if (_queue.TryGet(name, ref content))
			{

				if (content.Etag == filterContext.HttpContext.Request.Headers["If-None-Match"])
				{
					filterContext.HttpContext.Response.Set304(content.UpdateDate, DateTime.Now.AddMinutes(30));
					filterContext.Result = new EmptyResult();
				}
				else
				{
					filterContext.HttpContext.Response.SetEtag(content.Etag, content.UpdateDate, DateTime.Now.AddMinutes(30));
					filterContext.Result = content.Content;
				}
			}

			base.OnActionExecuting(filterContext);
		}

		public override void OnActionExecuted(ActionExecutedContext filterContext)
		{
			if (IsDebug)
				return;

			if (!(filterContext.Result is ViewResult))
			{
				base.OnActionExecuted(filterContext);
				return;
			}

			if (filterContext.HttpContext.User.Identity.IsAuthenticated)
			{
				filterContext.HttpContext.Response.ExpireUserEtag();
				base.OnActionExecuted(filterContext);
				return;
			}

			var name = GetName(filterContext.RouteData);

			if (name == null)
			{
				base.OnActionExecuted(filterContext);
				return;
			}

			ContextResultObject content = null;
			if (!_queue.TryGet(name, ref content))
			{
				using (StringWriter sw = new StringWriter())
				{
					ViewEngineResult viewResult = ViewEngines.Engines.FindView(filterContext,
																																		 filterContext.RouteData.GetRequiredString("action"),
																																		 null);
					ViewContext viewContext = new ViewContext(filterContext, viewResult.View, filterContext.Controller.ViewData, filterContext.Controller.TempData, sw);
					viewResult.View.Render(viewContext, sw);

					content = new ContextResultObject
											{
												Content = new ContentResult { Content = sw.GetStringBuilder().ToString() },
												Hash = (string)filterContext.Controller.ViewData[HashByViewData],
												UpdateDate = (DateTime)filterContext.Controller.ViewData[UpdateDateByViewData],
											};

					_queue.AddOrReplace(name, content, DateTime.Now.AddYears(1));
				}
			}

			filterContext.HttpContext.Response.SetEtag(content.Etag, content.UpdateDate, DateTime.Now.AddMinutes(30));
			filterContext.Result = content.Content;

			base.OnActionExecuted(filterContext);
		}
	}
}